<template>
  <div
    class="absolute inset-0 flex justify-center items-center select-none"
    @dblclick="handleOnTop"
  >
    <div ref="refContainer" class="fit-box overflow-hidden relative">
      <img
        ref="refPan"
        src="/pan.png"
        class="absolute left-1/2 transform -translate-x-1/2 object-contain h-full"
      />
      <div class="absolute inset-0 flex justify-center items-center">
        <div
          ref="refNeedle"
          class="w-[12%] h-full relative"
          :style="{ animationDuration: `${animationDuration}s` }"
        >
          <img
            src="/needle.png"
            class="absolute bottom-[1.5%] object-contain drop-shadow-[-15px_6px_12px_rgba(0,0,0,0.35)]"
          />
        </div>
      </div>
      <img
        ref="refMask"
        src="/mask.png"
        class="absolute left-1/2 transform -translate-x-1/2 object-contain bottom-0"
      />
      <div
        class="text-stroke-black absolute justify-center items-center"
        :style="{
          fontSize: `${fonSize}px`,
          lineHeight: `${fonSize}px`,
          ...positions[fontPosition],
        }"
      >
        {{ showText }}
      </div>
      <div
        class="text-black absolute right-2 top-2 text-xl cursor-pointer"
        @click="handleShowSettings"
      >
        {{ bpm }}bpm {{ beat }}beat
      </div>
    </div>
  </div>
  <el-dialog v-model="isDialogVisible" title="参数设置" @close="updateParams">
    <el-form label-width="auto">
      <el-form-item label="预设">
        <el-button-group>
          <el-button v-for="(p, idx) in presets" :key="idx" @click="handleLoadPresets(p)">
            {{ p.bpm + '/' + p.beat }}
          </el-button>
        </el-button-group>
      </el-form-item>
      <el-form-item label="BPM">
        <el-select v-model="bpm" filterable allow-create placeholder="Bpm">
          <el-option v-for="(item, index) in bpmPresets" :key="index" :label="item" :value="item" />
        </el-select>
      </el-form-item>
      <el-form-item label="节拍">
        <el-input-number v-model="beat" :min="1" :max="16" />
      </el-form-item>
      <el-form-item label="音效">
        <el-select v-model="audioMode" placeholder="音效">
          <el-option
            v-for="(item, index) in modes"
            :key="index"
            :label="item.label"
            :value="item.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="字体">
        <el-select v-model="fontZoom" placeholder="字体" @change="resize">
          <el-option
            v-for="(item, index) in fontSizeZooms"
            :key="index"
            :label="item"
            :value="item"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="位置">
        <el-select v-model="fontPosition" placeholder="位置">
          <el-option
            v-for="(item, index) in positionOptions"
            :key="index"
            :label="item.label"
            :value="item.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="音量">
        <el-slider v-model="volume" />
      </el-form-item>
    </el-form>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { WindowSetAlwaysOnTop, WindowSetTitle } from '@/wailsjs/runtime/runtime'

interface AudioInfo {
  audio: HTMLAudioElement
  duration: number
}
type AudioMode = 'human' | 'di' | 'machine'
type FontPosition = 'leftTop' | 'center' | 'bottomCenter' | 'topCenter'
let audioInfoList: AudioInfo[] = []
const refNeedle = ref<HTMLElement | null>(null)
let isForward = true
const isDialogVisible = ref(false)
const presets = [
  { bpm: '60', beat: 4 },
  { bpm: '120', beat: 8 },
  { bpm: '150', beat: 10 },
  { bpm: '180', beat: 12 },
  { bpm: '240', beat: 16 },
]
const bpm = ref('60')
const bpmPresets = ['30', '40', '60', '80', '100', '120', '140', '160', '180']
const cnt = ref(0)
const refContainer = ref<HTMLElement | null>(null)
const fonSize = ref(64)
const fontSizeZooms = [0, 0.5, 1, 1.5, 2]
const fontZoom = ref(1)
const beat = ref(4)
let timer: number | null = null
const audioMode: Ref<AudioMode> = ref('human')
const modes = [
  {
    value: 'human',
    label: '人声',
  },
  {
    value: 'di',
    label: '嘀声',
  },
  {
    value: 'machine',
    label: '机器声',
  },
]
const volume = ref(100)
const refPan = ref<HTMLElement | null>(null)
const refMask = ref<HTMLElement | null>(null)
const positions = {
  leftTop: {
    left: '1rem',
    top: '1rem',
    transform: 'translate(0, 0)',
  },
  center: {
    left: '50%',
    top: '50%',
    transform: 'translate(-50%, -50%)',
  },
  bottomCenter: {
    left: '50%',
    bottom: '1rem',
    transform: 'translate(-50%, 0)',
  },
  topCenter: {
    left: '50%',
    top: '1rem',
    transform: 'translate(-50%, 0)',
  },
}

const positionOptions = [
  {
    label: '左上角',
    value: 'leftTop',
  },
  {
    label: '居中',
    value: 'center',
  },
  {
    label: '底部居中',
    value: 'bottomCenter',
  },
  {
    label: '顶部居中',
    value: 'topCenter',
  },
]

const fontPosition: Ref<FontPosition> = ref('center')

let isOnTop = false
const handleOnTop = () => {
  isOnTop = !isOnTop
  WindowSetAlwaysOnTop(isOnTop)
  ElMessage({
    message: isOnTop ? '置顶' : '取消置顶',
    type: 'success',
  })
  if (isOnTop) {
    WindowSetTitle('节拍器 - 置顶')
  } else {
    WindowSetTitle('节拍器')
  }
}
const handleShowSettings = () => {
  isDialogVisible.value = true
  stop()
}

const handleAnimationEnd = () => {
  isForward = !isForward
  if (refNeedle.value) {
    refNeedle.value.classList.remove('animate-rotate-forward', 'animate-rotate-backward')
    // Reapply the class to trigger the animation again
    refNeedle.value?.classList.add(isForward ? 'animate-rotate-forward' : 'animate-rotate-backward')
    if (++cnt.value >= beat.value) {
      cnt.value = 0
    }

    playNext((cnt.value + 1) % beat.value)
  }
}

const animationDuration = computed(() => {
  return 60 / parseInt(bpm.value)
})

const showText = computed(() => {
  if (audioMode.value === 'human' && cnt.value >= 10) {
    return String.fromCharCode(65 + cnt.value - 10)
  }
  return `${cnt.value + 1}`
})
const resize = () => {
  if (refContainer.value) {
    const containerWidth = refContainer.value.clientWidth
    let clc = containerWidth / 4
    if (clc < 36) {
      clc = 36
    } else if (clc > 384) {
      clc = 384
    }
    fonSize.value = clc * fontZoom.value
  }
  if (refPan.value && refMask.value) {
    const rect = refPan.value.getBoundingClientRect()
    const width = rect.width
    const left = rect.left
    console.log(width, left)
    if (width && refMask.value) {
      refMask.value.style.width = `${width}px`
      // refMask.value.style.left = `${left}px`
    }
  }
}

const updateParams = () => {
  localStorage.setItem('bpm', bpm.value)
  localStorage.setItem('fontZoom', fontZoom.value.toString())
  localStorage.setItem('fontPosition', fontPosition.value)
  localStorage.setItem('beat', beat.value.toString())
  localStorage.setItem('audioMode', audioMode.value)
  localStorage.setItem('volume', volume.value.toString())
  start()
}

function loadMeta(src: string): Promise<AudioInfo> {
  return new Promise((resolve, reject) => {
    const audio = new Audio(src)
    audio.addEventListener('loadedmetadata', () => {
      if (isNaN(audio.duration)) {
        reject(new Error('Failed to load audio'))
      }
      resolve({ audio, duration: audio.duration })
    })
    audio.addEventListener('error', () => {
      reject(new Error('Failed to load audio'))
    })
    audio.volume = volume.value / 100
    audio.load()
  })
}

async function loadAudio(mode: AudioMode) {
  audioInfoList = []
  try {
    switch (mode) {
      case 'human':
        for (let i = 1; i <= 16; i++) {
          audioInfoList.push(await loadMeta(`/audio/${i}.mp3`))
        }
        break
      case 'di':
        for (let i = 1; i <= 2; i++) {
          audioInfoList.push(await loadMeta(`/audio/di${i}.mp3`))
        }
        break
      case 'machine':
        audioInfoList.push(await loadMeta(`/audio/machine.mp3`))
        break
    }
  } catch {
    ElMessage.error('Failed to load audio')
    audioInfoList = []
  }
}

function playNext(index: number) {
  let nextIndex = index
  switch (audioMode.value) {
    case 'di':
      nextIndex = index == 0 ? 0 : 1
      break
    case 'machine':
      nextIndex = 0
      break
  }
  if (timer) {
    clearTimeout(timer)
  }
  timer = setTimeout(
    () => {
      audioInfoList[nextIndex].audio.play()
    },
    (animationDuration.value - audioInfoList[nextIndex].duration / 2) * 1000,
  )
}

function start() {
  loadAudio(audioMode.value).then(() => {
    if (refNeedle.value) {
      refNeedle.value.classList.add('animate-rotate-forward')
      audioInfoList[0].audio.play()
      playNext(audioMode.value == 'machine' ? 0 : 1)
    }
  })
}

function stop() {
  if (timer) {
    clearTimeout(timer)
  }
  if (refNeedle.value) {
    refNeedle.value.classList.remove('animate-rotate-forward', 'animate-rotate-backward')
  }
  cnt.value = 0
  isForward = true
}

const handleLoadPresets = (p: { bpm: string; beat: number }) => {
  bpm.value = p.bpm
  beat.value = p.beat
}

const keyWatcher = (e: KeyboardEvent) => {
  if (e.key === 'Escape' || e.key === ' ') {
    if (!isDialogVisible.value) {
      handleShowSettings()
    }
  }
}

onMounted(() => {
  if (refNeedle.value) {
    refNeedle.value.addEventListener('animationend', handleAnimationEnd)
  }
  if (refPan.value) {
    refPan.value.addEventListener('load', () => {
      resize()
    })
  }
  window.addEventListener('resize', resize)
  window.addEventListener('keydown', keyWatcher)
  bpm.value = localStorage.getItem('bpm') || '60'
  fontZoom.value = parseFloat(localStorage.getItem('fontZoom') || '1')
  beat.value = parseInt(localStorage.getItem('beat') || '4')
  audioMode.value = (localStorage.getItem('audioMode') as AudioMode) || 'human'
  volume.value = parseInt(localStorage.getItem('volume') || '100')
  fontPosition.value = (localStorage.getItem('fontPosition') as FontPosition) || 'center'
  start()
})
onUnmounted(() => {
  if (refNeedle.value) {
    refNeedle.value.removeEventListener('animationend', handleAnimationEnd)
  }
  window.removeEventListener('resize', resize)
  window.removeEventListener('keydown', keyWatcher)
})
</script>

<style scoped lang="scss">
@use 'sass:math';

$angle: 25deg;
$ratio: 2 * math.tan($angle); // 自动转弧度

.fit-box {
  aspect-ratio: $ratio;
}

/* 当容器比较“宽”，让 fit-box 的宽度占满 */
@media (min-aspect-ratio: $ratio) {
  .fit-box {
    height: 100%;
    width: auto;
  }
}

/* 当容器比较“高”，让 fit-box 的高度占满 */
@media (max-aspect-ratio: $ratio) {
  .fit-box {
    width: 100%;
    height: auto;
  }
}

@keyframes rotate-forward {
  0% {
    transform: rotate(-$angle);
    transform-origin: bottom center;
  }
  100% {
    transform: rotate($angle);
    transform-origin: bottom center;
  }
}

@keyframes rotate-backward {
  0% {
    transform: rotate($angle);
    transform-origin: bottom center;
  }
  100% {
    transform: rotate(-$angle);
    transform-origin: bottom center;
  }
}

.animate-rotate-forward {
  animation: rotate-forward 1 forwards cubic-bezier(0.42, 0, 0.58, 1);
}

.animate-rotate-backward {
  animation: rotate-backward 1 forwards cubic-bezier(0.42, 0, 0.58, 1);
}

.text-stroke-black {
  -webkit-text-stroke: 2px black;
  color: white;
}

.needle-shadow {
  position: relative;
}

.needle-shadow::after {
  content: '';
  position: absolute;
  bottom: 1.5%;
  left: 0;
  width: 100%;
  height: 100%;
  background: url('/needle.png') no-repeat center center;
  background-size: contain;
  color: black;
  filter: blur(8px) opacity(0.55);
  transform: translate(-15px, 10px) scale(1.02); /* 阴影偏移方向和大小 */
  z-index: -1;
  pointer-events: none;
}
</style>
